# 作者: YYJ
# 2025年07月08日13时23分06秒
# 2486249622@qq.com


import socket
import sys
import select


class ChatServer:
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 设置端口复用
        self.server.bind((self.ip, self.port))
        self.server.listen(10)
        self.epoll = select.epoll()
        self.epoll.register(self.server.fileno(), select.EPOLLIN)
        self.epoll.register(sys.stdin.fileno(), select.EPOLLIN)

        # 保存客户端连接
        self.connections = {}

    def broadcast(self, sender_fd, message):
        for fd, client_socket in list(self.connections.items()):
            if fd != sender_fd:
                try:
                    client_socket.sendall(message.encode('utf-8'))
                except:
                    self.handle_client_disconnect(fd)

    def handle_client_disconnect(self, fd):
        # 先从 epoll 取消注册
        try:
            self.epoll.unregister(fd)
        except KeyError:
            # 如果 fd 没有被注册过，忽略异常
            pass

        # 再关闭 socket
        client = self.connections.pop(fd, None)
        if client:
            client.close()

        print(f"Client({fd}) disconnected")

    def chat(self):
        while True:
            events = self.epoll.poll(-1)
            for fd, event in events:
                if fd == self.server.fileno():  # 如果服务器触发，则接受连接
                    conn, addr = self.server.accept()
                    print("Connected by", addr)
                    conn.setblocking(False)
                    self.epoll.register(conn.fileno(), select.EPOLLIN)
                    self.connections[conn.fileno()] = conn
                elif fd == sys.stdin.fileno():  # 如果标准输入触发，则从标准输入中获取数据，并写入连接
                    data = sys.stdin.readline().strip()
                    if data:
                        self.broadcast(sys.stdin.fileno(), f"Server: {data}\n")

                elif fd in self.connections:  # 如果连接触发，则从连接中获取数据，并打印到标准输出
                    if event & select.EPOLLIN:
                        client_socket = self.connections.get(fd)
                        if not client_socket:
                            continue
                        try:
                            data = client_socket.recv(1024).decode('utf-8')
                            if data:
                                print(f"Client({fd}): {data}")
                                self.broadcast(fd, f"Client:{fd}: {data}\n")
                            else:
                                self.handle_client_disconnect(fd)
                        except ConnectionError:
                            self.handle_client_disconnect(fd)
                    else:
                        self.handle_client_disconnect(fd)
                else:
                    print("Invalid fd")


if __name__ == '__main__':
    server = ChatServer('127.0.0.1', 8000)
    server.chat()
